package ar.edu.untref.lp1.plomeria.modelos;

import ar.edu.untref.lp1.plomeria.helpers.HelperParedDeMaterial;


public class Tablero {

	/* Traduce el array de Material a un array de Casilleros */
	private Casillero[][] mapa;

	public Tablero(Material[][] mapaDeMateriales) {
		if (mapaDeMateriales.length < 1) {
			throw new Error(
					"La cantidad de columnas no puede ser menor que 1, se recibio "
							+ mapaDeMateriales.length);
		}
		if (mapaDeMateriales[0].length < 1) {
			throw new Error(
					"La cantidad de filas no puede ser menor que 1, se recibio "
							+ mapaDeMateriales[0].length);
		}

		this.mapa = this.crearMapaDeCasilleros(mapaDeMateriales);
		this.validarTablero(this.mapa);
	}

	/**
	 * pre: recibe un array[][] de casilleros, que debe cumplir las siguientes
	 * caracteristicas: - un solo elemento "caño_horizontal" en la primera
	 * columna - un solo elemento "caño vertical" en la ultima columna -
	 * cualquier cantidad de caños horizontales, pero solo uno por columna - de
	 * un caño horizontal que no sea el primerod e la izquierda o primero de la
	 * derecha, debe tener columnas VACIAS (ladrillo) la columna anterior y
	 * posterior.
	 * 
	 * post: si la validacion falla arroja una exepcion
	 */
	private void validarTablero(Casillero[][] mapa) {

		this.validarPrimerColumna(mapa);

		this.validarUltimaColumna(mapa);

		this.validarColumnasIntermedias(mapa);

	}

	/**
	 * Post: valida condicion de que haya 1 y solo un caño sobre la primera
	 * columna (izquierda) y que esta columna sea un caño horizontal
	 */
	private void validarPrimerColumna(Casillero[][] mapa) {
		int cantidadDeCañosHorizontales = 0;

		/*
		 * conteo de Material no ladrillo en la primera columna (recorre sus
		 * filas)
		 */
		for (int filasDecolumnaInicial = 0; filasDecolumnaInicial < mapa[0].length; filasDecolumnaInicial++) {
			switch (mapa[0][filasDecolumnaInicial].getMaterial()) {
			case LADRILLO:
				break;
			case CAÑO_HORIZONTAL:
				cantidadDeCañosHorizontales++;
				break;
			default:
				Error errorValidacionPrimeraFila = new Error("En la primera columna se encontro un Material distinto de Ladrillo o Caño Horizontal. Verifique la Matriz.");
				throw errorValidacionPrimeraFila;
			}
		}
		
		if(cantidadDeCañosHorizontales == 0 || cantidadDeCañosHorizontales > 1){
			Error errorValidacionPrimeraFila = new Error("En la primera columna se encontro "+cantidadDeCañosHorizontales+" del Caño Horizontal, debe haber obligatoriamente 1. Verifique la Matriz.");
			throw errorValidacionPrimeraFila;
		}
	}

	/**
	 * Post: valida condicion de que haya 1 y solo un caño sobre la ultima
	 * columna (derecha) y que esta columna sea un caño horizontal
	 */
	private void validarUltimaColumna(Casillero[][] mapa) {
		int cantidadDeCañosHorizontales = 0;

		/*
		 * conteo de Material no ladrillo en la ultima columna (recorre sus
		 * filas)
		 */
		for (int filasDecolumnaFinal = 0; filasDecolumnaFinal < mapa[mapa.length -1].length; filasDecolumnaFinal++) {
			switch (mapa[mapa.length -1][filasDecolumnaFinal].getMaterial()) {
			case LADRILLO:
				break;
			case CAÑO_HORIZONTAL:
				cantidadDeCañosHorizontales++;
				break;
			default:
				Error errorValidacionPrimeraFila = new Error("En la ultima columna se encontro un Material distinto de Ladrillo o Caño Horizontal. Verifique la Matriz.");
				throw errorValidacionPrimeraFila;
			}
		}
		
		if(cantidadDeCañosHorizontales == 0 || cantidadDeCañosHorizontales > 1){
			Error errorValidacionPrimeraFila = new Error("En la ultima columna se encontro "+cantidadDeCañosHorizontales+" del Caño Horizontal, debe haber obligatoriamente 1. Verifique la Matriz.");
			throw errorValidacionPrimeraFila;
		}
	}
	/**
	 * valida que 
	 * 1) en ningun caso haya mas de un caño en cada columna
	 * intermedia entre la primera y la ultima 
	 * 2) que los caños que se encuentren sean caños horizontales 
	 * 3) que para cada columna ocupada con caño, la anterior y posterior no 
	 * tengan ningun caño El incumplimiento de cualquiera de estas condiciones 
	 * lanza una excepcion, interrumpiendo la ejecucion
	 * 
	 * PRE: recibe una matriz de Casillero[][] que representa la pared o tablero
	 * sobre la que se va a hacer el trabajo.
	 * 
	 * @param Casillero[][] mapa2
	 */
	private void validarColumnasIntermedias(Casillero[][] mapa2) {
		boolean validado = true;
		int fila = 0;
		
		if(mapa[0].length > 4){
			
			for(int columna = 1; columna < mapa2.length-1; columna++){
				int caños = 0;
				for(fila = 0; fila < mapa2[0].length; fila++){
					
					
					if(mapa[columna][fila].getMaterial() == Material.CAÑO_HORIZONTAL){
						caños++;
						
						int columnaSig = columna-1;
						int recorrer = 0;
						
						while(recorrer < mapa2[0].length && validado){
							validado = mapa2[columnaSig][recorrer].getMaterial() == Material.LADRILLO;
							recorrer++;
						}
						
						recorrer = 0;
						columnaSig = columna+1;
						
						while(recorrer < mapa2[0].length && validado){
							validado = mapa2[columnaSig][recorrer].getMaterial() == Material.LADRILLO;
							recorrer++;
						}
						
					}
					if(!validado){
						Error cañoMalColocado = new Error("Caño mal colocado en columna: "+columna+" y Fila: "+fila);
						throw cañoMalColocado;
					}else if(caños > 1){
						Error cañoDuplicado = new Error("Caño duplicado en columna: "+columna+" y Fila: "+fila);
						throw cañoDuplicado;
					}
				}
			}
		}
	}
		

	/**
	 * pre: se ingresa tipo de material, la columna y la fila donde se va a
	 * colocar
	 * 
	 * post: setea un caño en un casillero, o error si a) el indice esta fuera del tablero, el casillero
	 * tiene ya un caño, o se intenta setear null en lugar de un material.
	 */
	public void setMaterialCasillero(Material material, int columna, int fila) {
		if(this.getCasillero(columna, fila).getMaterial() != Material.LADRILLO){
			HelperParedDeMaterial pared = new HelperParedDeMaterial();
			pared.imprimeMatriz(this.getMatrizDelTablero());
			Error TieneQueHaberLadrillo = new Error("El casillero en el que intenta insertar el nuevo Material:"+material+
					" tiene asignado el Material:" +": "+this.getCasillero(columna, fila).getMaterial()+
					" en Columna: "+columna+" Fila: "+fila);
			throw TieneQueHaberLadrillo;
		}
		validarIndiceColumna(columna, this.mapa.length);
		validarIndiceFila(fila, this.mapa[0].length);
		this.mapa[columna][fila].setMaterial(material,columna,fila);
	}

	/**
	 * pre: se ingresa columna y fila para averiguar material post: retorna
	 * material
	 */
	public Material getMaterialCasillero(int columna, int fila) {

		validarIndiceColumna(columna, this.mapa.length);
		validarIndiceFila(fila, this.mapa[0].length);

		return this.mapa[columna][fila].getMaterial();
	}

	/**
	 * pre: ninguna
	 * 
	 * post: recibe un array Material[][] y retorna una array de casillero[][]
	 */
	private Casillero[][] crearMapaDeCasilleros(Material[][] mapaDeMateriales) {

		Casillero[][] mapaDeCasilleros = new Casillero[mapaDeMateriales.length][mapaDeMateriales[0].length];

		for (int columna = 0; columna < mapaDeCasilleros.length; columna++) {
			for (int fila = 0; fila < mapaDeCasilleros[columna].length; fila++) {
				//ARREGLADO
				mapaDeCasilleros[columna][fila] = new Casillero(
									mapaDeMateriales[columna][fila], columna , fila );
//				System.out.println("Material: "
//									+ mapaDeMateriales[columna][fila]);
			}
		}
		return mapaDeCasilleros;
	}

	/**
	 * @param columna
	 * @throws Error
	 */
	public void validarIndiceColumna(int columna, int largoDelArray)
			throws Error {
		if (!this.validarLargoDelArray(largoDelArray)) {
			Error largoDelArrayNegativo = new Error(
					"La cantidad de columnas debe ser mayor o igual que cero, se recibio "
							+ largoDelArray);
			throw largoDelArrayNegativo;
		}
		if (!this.validarIndice(columna, largoDelArray)) {
			Error indiceFueraDeRango = new Error(
					"El indice debe estar entre cero y columnas-1 , se recibio [indice:"
							+ columna + "] [columnas" + +largoDelArray + "]");
			throw indiceFueraDeRango;
		}
	}

	/**
	 * @param fila
	 * @throws Error
	 */
	public void validarIndiceFila(int fila, int largoDelArray) throws Error {
		if (!this.validarLargoDelArray(largoDelArray)) {
			Error largoDelArrayNegativo = new Error(
					"La cantidad de filas debe ser mayor o igual que cero, se recibio "
							+ largoDelArray);
			throw largoDelArrayNegativo;
		}
		/*
		 * // ERROR // CHEQUEAR ESTA PARTE //
		 */
		 if (!this.validarIndice(fila, largoDelArray)) {
			Error indiceFueraDeRango = new Error(
					"El indice debe estar entre cero y filas-1 , se recibio [indice:"
							+ fila + "] [filas" + +largoDelArray + "]");
			throw indiceFueraDeRango;
		}
	}

	/**
	 * pre: recibe el largo del array.
	 * 
	 * post: Si el largo del array es menor que cero, retorna false.
	 */
	public boolean validarLargoDelArray(int largoDelArray) {
		boolean validado = (largoDelArray > 0);
		return validado;
	}

	/**
	 * pre: recibe un valor de indice a utilizar, y el largo del array.
	 * 
	 * post: Si el indice es menor que cero o mayor que el largo, lanza un
	 * Error.
	 */
	public boolean validarIndice(int indice, int largoDelArray) {
		boolean validado = (indice >= 0 && indice < largoDelArray);
		return validado;
	}

	/**
	 * Post: @return cantidad de columnas de mapa
	 */
	public int getColumnas() {
		return this.mapa.length;
	}

	/**
	 * Post: @return cantidad de filas de mapa
	 */
	public int getFilas() {
		return this.mapa[0].length;
	}

	/**
	 * PRE: recibe una columna valida
	 * 
	 * POST: retorna un array de Casilleros con la columna completa.
	 */
	public Casillero[] getColumnaPorIndice(int columna){
		validarIndiceColumna(columna, this.mapa.length);
		return this.mapa[columna];
	}
	
	/**
	 * PRE: recibe una fila valida
	 * 
	 * POST: retorna un array de Casilleros con la fila completa.
	 */
	public Casillero[] getFilaPorIndice(int fila){
		validarIndiceFila(fila, this.mapa[0].length);
		Casillero[] filaCompleta = new Casillero[this.mapa[0].length];
		for(int columna = 0; columna < this.mapa.length ; columna++){
			filaCompleta[columna] = this.mapa[columna][fila];
		}
		return filaCompleta;
	}
	
	/**
	 * Post: devuelve mapa de material entero
	 */
	public Material[][] getMatrizDelTablero(){
		
		Material[][] mapaDeMaterial = new Material[this.mapa.length][mapa[0].length];

		for (int columna = 0; columna < mapaDeMaterial.length; columna++) {
			for (int fila = 0; fila < mapaDeMaterial[columna].length; fila++) {
				//ARREGLADO
				mapaDeMaterial[columna][fila] = this.mapa[columna][fila].getMaterial();
			}
		}
		return mapaDeMaterial;
	}
	
	public Casillero getCasillero(int columna, int fila){
		return this.mapa[columna][fila];
	}
	
	/**
	 * PRE: recibe un casillero del mapa
	 * POST: retorna el casillero de la siguiente fila misma columna, o null si era el ultimo de la fila
	 * 
	 * @param Casillero casileroActual
	 * @return Casillero
	 */
	public Casillero getSiguienteCasilleroDebajo(Casillero casilleroActual){
		Casillero casilleroDeAbajo;
		if(casilleroActual.getFila() < this.mapa[casilleroActual.getFila()].length -1 ){
			casilleroDeAbajo = this.getCasillero(casilleroActual.getColumna(), casilleroActual.getFila() +1 );
		}else{
			casilleroDeAbajo = this.getPrimerCasilleroDeSiguienteColumna(casilleroActual);
		}
		return casilleroDeAbajo;
	}
	
	/**
	 * PRE: recibe un casillero del mapa
	 * POST: retorna el casillero fila cero de la siguiente columna, o null si era un casillero de la ultima columna
	 * 
	 * @param Casillero casileroActual
	 * @return Casillero
	 */
	public Casillero getPrimerCasilleroDeSiguienteColumna(Casillero casilleroActual){
		Casillero primerCasilleroDeLaSiguienteColumna;
		if(casilleroActual.getColumna() < this.mapa.length -1 ){
			primerCasilleroDeLaSiguienteColumna = this.getCasillero(casilleroActual.getColumna() +1 , 0 );
		}else{
			primerCasilleroDeLaSiguienteColumna = null;
		}
		return primerCasilleroDeLaSiguienteColumna;
	}
}
